/**
 * 
 */
package sample.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author Niki
 * @version 创建时间：2017年2月27日 下午11:44:35 简单的Java NIO 客户端示例
 */
public class SimpleClient {
    private String hostname = "127.0.0.1";
    private int port = 9000;
    private SocketAddress address;

    private void init() {
        address = new InetSocketAddress(hostname, port);
    }

    /**
     * 将缓冲区的数据转换为String
     * 
     * @param buffer
     * @return
     */
    private String readByteBuffer(ByteBuffer buffer) {
        buffer.flip();
        byte readByte;
        StringBuffer result = new StringBuffer();
        while (buffer.hasRemaining()) {
            readByte = buffer.get();
            result.append((char) readByte);
        }
        return result.toString();
    }

    private void client_1() {
        try {
            SocketChannel client = SocketChannel.open(address);
            // 初始化缓存，并指定缓存大小
            ByteBuffer buffer = ByteBuffer.allocate(74);
            // 利用Channels新建一个输出通道
            WritableByteChannel out = Channels.newChannel(System.out);
            while (client.read(buffer) != -1) {
                // 转换缓存的状态为输出状态，缓存之前从client中读取数据状态为读取状态
                buffer.flip();
                out.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void client_2() {
        try {
            SocketChannel client = SocketChannel.open(address);
            // 设置客户端为非阻塞
            client.configureBlocking(false);
            ByteBuffer readBuffer = ByteBuffer.allocate(74);
            int readed;
            // 因为是非阻塞的，所以read方法不阻塞，循环判断直到所有的数据都读入到缓冲区
            while (true) {
                readed = client.read(readBuffer);
                if (readed == -1)
                    break;
            }
            String result = readByteBuffer(readBuffer);
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void client_3() {
        SocketChannel client = null ;
        Selector selector = null ;
        try {
            client = SocketChannel.open();
            // 设置为非阻塞模式
            client.configureBlocking(false);
            client.socket().connect(address);
            // 打开一个选择器，不同版本的jvm，选择器不同
            selector = Selector.open();
            /*
             * 向选择器阻塞通道， 最后一个参数为附加对象，可以为空 socketChannel.register(selector,
             * SelectionKey.OP_CONNECT) ;
             */
            client.register(selector, SelectionKey.OP_CONNECT, null);
            /*
             * 阻塞，等待至少有一个通道祖册的事件准备好。返回准备好的通道的个数
             * 也可以采用不阻塞的模式如方法：selector(timeout)或selecotrNOw() ;
             * 前面的方法最多阻塞timeout毫秒后返回 后面的方法则立即放回准备好的通道的个数，不会在方法出阻塞
             */
            int readedSelector = selector.select();
            if (readedSelector > 0) {
                Set<SelectionKey> selectorKeys = selector.selectedKeys();
                /*
                 * 不要用foreach进行遍历，因为foreach中不能对元素进行删除 for(SelectionKey
                 * selectorKey : selectorKeys){
                 */
                Iterator<SelectionKey> iterator = selectorKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectorKey = iterator.next();
                    
                    if (selectorKey.isConnectable()) {
                        if(client.isConnectionPending()){
                            if(client.finishConnect()){
                                //只有当连接成功才能注册read事件
                                selectorKey.interestOps(selectorKey.OP_READ) ;

                                //连接成功后，可以向服务器写数据
                                String str = "hello server !" ;
                                byte[] data = str.getBytes() ;
                                ByteBuffer buffer = ByteBuffer.allocate(data.length) ;
                                buffer.put(data) ;
                                client.write(buffer) ;
                            }
                        }
                    } else if (selectorKey.isReadable()) {// 已经准备好读取服务器端的数据到缓存
                        ByteBuffer buffer = ByteBuffer.allocate(128) ;
                        client.read(buffer) ;
                        readByteBuffer(buffer) ;
                    }
                    
                    /*
                     * Selector不会自己从已选择键集中移除SelectionKey实例。
                     * 必须在处理完通道时自己移除。下次该通道变成就绪时，Selector会再次将其放入已选择键集中。
                     */
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(client != null){
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(selector != null){
                try {
                    selector.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private void testBuffer(){
        ByteBuffer buffer = ByteBuffer.allocate(42);
        String test = "test";
        byte[] tests = test.getBytes();
        for (byte b : tests) {
            System.out.println(b);
        }
        buffer.put(test.getBytes());
        buffer.flip();
        StringBuffer result = new StringBuffer();
        byte resByte;
        while (buffer.hasRemaining()) {
            resByte = buffer.get();
            result.append((char) resByte);
        }
        buffer.clear();
        System.out.println(result.toString());
    }
    
    public static void main(String[] args) {
        SimpleClient client = new SimpleClient() ;
        client.init();
        client.client_3(); 
    }
}
